package com.darin.cl.http;

import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.RandomAccessFile;
import java.io.UnsupportedEncodingException;
import java.net.URI;
import java.net.URLEncoder;
import java.security.KeyManagementException;
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;

import javax.net.ssl.SSLException;
import javax.net.ssl.SSLPeerUnverifiedException;

import org.apache.http.Consts;
import org.apache.http.Header;
import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.HttpStatus;
import org.apache.http.client.HttpClient;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.HttpEntityEnclosingRequestBase;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.methods.HttpPut;
import org.apache.http.client.methods.HttpRequestBase;
import org.apache.http.client.params.HttpClientParams;
import org.apache.http.conn.scheme.Scheme;
import org.apache.http.conn.scheme.SchemeRegistry;
import org.apache.http.entity.ByteArrayEntity;
import org.apache.http.entity.ContentType;
import org.apache.http.entity.mime.HttpMultipartMode;
import org.apache.http.entity.mime.MultipartEntityBuilder;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.impl.conn.SingleClientConnManager;
import org.apache.http.message.BasicHeader;
import org.apache.http.message.BasicNameValuePair;
import org.apache.http.params.BasicHttpParams;
import org.apache.http.params.HttpConnectionParams;
import org.apache.http.params.HttpParams;
import org.apache.http.protocol.HTTP;

import android.net.Uri;
import android.util.Log;

import com.darin.cl.util.CLFileUtil;

/**
 * Http请求封装类
 * 
 * @author Jianfeng.Lao
 * 
 */
public class CLHttpUtil {
	private static final String TAG = "CLHttpUtil";

	protected HttpClient mClient;
	protected HttpParams mHttpParams = new BasicHttpParams();
	protected List<Header> mHeaderList = new ArrayList<Header>();
	protected HttpRequestBase mHttpRequest;
	protected List<BasicNameValuePair> mRequestParams = new ArrayList<BasicNameValuePair>();
	protected Map<String, File> mFileParams = null;
	protected Map<String, Object> mParamsFlag = new HashMap<String, Object>();
	private String mUrl;
	private int mSocketBufferSize = 8192;
	private int mSocketTimeOut = 30 * 1000;
	private int mConnectTimeOut = 30 * 1000;
	private String mJsonParam;

	public CLHttpUtil(String url) {
		super();
		this.mUrl = url;
	}

	public void addParam(String name, Object value) {
		this.addParam(name, value, null);
	}

	public void addParam(String name, Object value, String paramFlag) {
		this.addParam(name, value, false, paramFlag);
	}

	public void addParamEncode(String name, Object value) {
		this.addParamEncode(name, value, null);
	}

	public void addParamEncode(String name, Object value, String paramFlag) {
		this.addParam(name, value, true, paramFlag);
	}

	public void addHeader(String name, String value) {
		Header header = new BasicHeader(name, value);
		mHeaderList.add(header);
	}

	public void setJsonParam(String json) {
		this.mJsonParam = json;
	}

	private void addParam(String name, Object value, boolean needEncode, Object paramFlag) {
		if (name != null && value != null) {
			if (paramFlag != null) {
				this.mParamsFlag.put(name, paramFlag);
			}
			if (value instanceof File) {
				File fileValue = (File) value;
				if (mFileParams == null) {
					mFileParams = new HashMap<String, File>();
				}
				this.mFileParams.put(name, fileValue);
			} else {
				String paramName = name;
				String paramValue = null;
				if (value instanceof Collection<?>) {
					Collection<?> collection = (Collection<?>) value;
					if (collection.size() > 0) {
						StringBuffer str = new StringBuffer("");
						for (Object obj : collection) {
							if (!str.toString().equals("")) {
								str.append(",");
							}
							str.append(obj);
						}
						paramValue = str.toString();
					}
				} else {
					paramValue = value.toString();
				}
				if (paramValue != null) {
					if (needEncode) {
						try {
							paramValue = URLEncoder.encode(paramValue, "utf-8");
						} catch (UnsupportedEncodingException e) {
							e.printStackTrace();
						}
					}
					mRequestParams.add(new BasicNameValuePair(paramName, paramValue));
				}
			}
		}
	}

	protected void setDefaultParams() {
		HttpConnectionParams.setConnectionTimeout(mHttpParams, mConnectTimeOut);
		HttpConnectionParams.setSoTimeout(mHttpParams, mSocketTimeOut);
		HttpConnectionParams.setSocketBufferSize(mHttpParams, mSocketBufferSize);
		HttpClientParams.setRedirecting(mHttpParams, true);
	}

	/**
	 * 返回响应内容
	 * 
	 * @param method
	 * @param type
	 * @return
	 * @throws CLConnectionException
	 * @throws CLInvalidNetworkException
	 * @throws IOException
	 */
	public String getResponseString(RequestMethod method, RequestBobyType type)
			throws CLConnectionException, CLInvalidNetworkException {
		String result = null;
		try {
			HttpResponse response = startConnection(method, type, null);
			if (response != null && response.getStatusLine().getStatusCode() == HttpStatus.SC_OK) {
				result=getContentByResponse(response);
			}
		} catch (IllegalStateException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		} finally {
			shutdown();
		}
		return result;
	}

	/**
	 * 自定义处理响应
	 * 
	 * @param method
	 * @param type
	 * @param listener
	 * @return
	 * @throws CLConnectionException
	 * @throws CLInvalidNetworkException
	 */
	public HttpResponse obtainResponse(RequestMethod method, RequestBobyType type,
			CLHttpProgressImpl listener) throws CLConnectionException, CLInvalidNetworkException {
		return startConnection(method, type, listener);
	}

	public void upLoadFile(CLHttpProgressImpl listener) throws Exception {
		InputStream input = null;
		try {
			HttpResponse response = startConnection(RequestMethod.POST, RequestBobyType.FORM_DATA,
					listener);
			if (response != null && listener != null) {
				int responseCode = response.getStatusLine().getStatusCode();
				listener.setResponseCode(responseCode);
				input = response.getEntity().getContent();
				ByteArrayOutputStream baos = null;
				if (input != null) {
					baos = new ByteArrayOutputStream();
					writeData(0, 0, null, input, baos, null);
					listener.onResponse(baos.toByteArray());
				}

			}
		} catch (Exception e) {
			e.printStackTrace();
			throw e;
		} finally {
			CLFileUtil.closeIOStream(input);
			shutdown();
		}
	}

	/**
	 * 需要支持断点续传服务器下载文件,请不要用addHeader方法增加Range属性
	 * 
	 * @param filePath
	 * @param type
	 * @param listener
	 * @author Jianfeng.Lao
	 * @throws Exception 
	 * @CreateDate 2014年3月7日
	 */
	public void downloadFileWithANR(String filePath, CLHttpProgressImpl listener) throws Exception {
		InputStream input = null;
		RandomAccessFile ras = null;
		try {
			long fileSize = 0;
			long contentLength = -1;
			File downloadFile = new File(filePath);
			if (downloadFile.exists()) {
				Log.v(TAG, "downloadFile exist");
				fileSize = downloadFile.length();
			}
			Log.v(TAG, "downloadFileSize>>" + fileSize);
			HttpResponse test_response = startConnection(RequestMethod.GET, null, listener);
			if (test_response != null) {
				Header length = test_response.getFirstHeader("Content-Length");
				if (length != null) {
					try {
						contentLength = Long.parseLong(length.getValue());
					} catch (Exception e) {
						e.printStackTrace();
					}
				}
				if (contentLength != 0 && contentLength == fileSize) {
					if (listener != null) {
						listener.onProcessEnd();
					}
					return;
				}
			}
			shutdown();
			String range = "bytes=" + fileSize + "-" + contentLength;
			addHeader("Range", range);
			Log.v(TAG, "range:" + range);
			HttpResponse response = startConnection(RequestMethod.GET, null, listener);
			if (response != null) {
				int responseCode = response.getStatusLine().getStatusCode();
				if (listener != null) {
					listener.setResponseCode(responseCode);
				}
				Log.v(TAG, "responseCode>>" + responseCode);
				if (responseCode == 206) {
					input = response.getEntity().getContent();
					ras = new RandomAccessFile(filePath, "rw");
					ras.seek(fileSize);
					if (input != null) {
						writeData(fileSize, contentLength, filePath, input, ras, listener);
					}
				}
				if (listener != null) {
					listener.onProcessEnd();
				}
			}
		} catch (Exception e) {
			e.printStackTrace();
			throw e;
		} finally {
			CLFileUtil.closeIOStream(input);
			CLFileUtil.closeIOStream(ras);
			shutdown();
		}
	}

	/**
	 * 下载文件
	 * 
	 * @param filePath
	 * @param method
	 * @param type
	 * @param listener
	 * @author Jianfeng.Lao
	 * @throws Exception 
	 * @CreateDate 2014年3月6日
	 */
	public void downloadFile(String filePath, RequestMethod method, RequestBobyType type,
			CLHttpProgressImpl listener) throws Exception {
		download(filePath, listener, method, type);
	}

	private void download(String filePath, CLHttpProgressImpl listener, RequestMethod mothed,
			RequestBobyType type) throws Exception {
		InputStream input = null;
		FileOutputStream fos = null;
		try {
			HttpResponse response = startConnection(mothed, type, listener);
			if (response != null) {
				int responseCode = response.getStatusLine().getStatusCode();
				if (listener != null) {
					listener.setResponseCode(responseCode);
				}
				long contentLength = -1;
				Header length = response.getFirstHeader("Content-Length");
				if (length != null) {
					try {
						contentLength = Long.parseLong(length.getValue());
					} catch (Exception e) {
						e.printStackTrace();
					}
				}
				if (responseCode == 200) {
					input = response.getEntity().getContent();
					fos = new FileOutputStream(filePath);
					if (input != null) {
						writeData(0, contentLength, filePath, input, fos, listener);
					}
					fos.flush();
				}

				if (listener != null) {
					listener.onProcessEnd();
				}
			}
		} catch (Exception e) {
			e.printStackTrace();
			throw e;
		} finally {
			CLFileUtil.closeIOStream(input);
			CLFileUtil.closeIOStream(fos);
			shutdown();
		}
	}

	protected HttpResponse startConnection(RequestMethod method, RequestBobyType requestBobyType,
			CLHttpProgressImpl listener) throws CLConnectionException, CLInvalidNetworkException {
		HttpResponse response = null;
		if (method == null) {
			return null;
		}
		try {
			mClient = createHttpClient(mUrl);
			HttpRequestBase request = null;
			if (RequestMethod.POST.equals(method)) {
				request = new HttpPost();
			} else if (RequestMethod.GET.equals(method)) {
				request = new HttpGet();
			} else if (RequestMethod.PUT.equals(method)) {
				request = new HttpPut();
			}
			mHttpRequest = request;
			// 判断请求类型设置参数
			if (RequestMethod.POST.equals(method) || RequestMethod.PUT.equals(method)) {
				// POST或都PUT请求处理
				HttpEntity httpEntity = null;
				if (RequestBobyType.FORM_URLENCODED.equals(requestBobyType)) {
					httpEntity = new UrlEncodedFormEntity(mRequestParams, HTTP.UTF_8);
				} else if (RequestBobyType.FORM_DATA.equals(requestBobyType)) {
					MultipartEntityBuilder build = MultipartEntityBuilder.create();
					if (mFileParams != null) {
						// 上传文件设置
						for (Entry<String, File> map : mFileParams.entrySet()) {
							Object flag = null;
							if (mParamsFlag.containsKey(map.getKey())) {
								flag = mParamsFlag.get(map.getKey());
							}
							CLFileBody fileBoby = new CLFileBody(map.getValue(), flag);
							fileBoby.setUpLoadProgressListener(listener);
							build.addPart(map.getKey(), fileBoby);
							Log.d(TAG, "key>>" + map.getKey() + ",file>>"
									+ map.getValue().getAbsolutePath() + ",flag>>" + flag);
						}
						// 上传参数设置
						for (BasicNameValuePair param : mRequestParams) {
							build.addTextBody(param.getName(), param.getValue(), ContentType
									.create(ContentType.DEFAULT_TEXT.getMimeType(), Consts.UTF_8));
						}
						build.setMode(HttpMultipartMode.RFC6532);// 解决中文乱码问题
						httpEntity = build.build();
						// Log.d(TAG, "Size>>"+httpEntity.getContentLength());
					}
				} else if (RequestBobyType.APP_JSON.equals(requestBobyType)) {
					if (mJsonParam != null) {
						byte[] jsonByte = null;
						jsonByte = mJsonParam.toString().getBytes();
						httpEntity = new ByteArrayEntity(jsonByte);
						((ByteArrayEntity) httpEntity).setContentEncoding(new BasicHeader(
								HTTP.CONTENT_TYPE, ContentType.APPLICATION_JSON.getMimeType()));
					}
				}
				((HttpEntityEnclosingRequestBase) request).setEntity(httpEntity);
			} else if (RequestMethod.GET.equals(method)) {
				// GET 请求处理
				StringBuffer sb = new StringBuffer(this.mUrl);
				for (BasicNameValuePair pair : mRequestParams) {
					if (sb.toString().indexOf("?") >= 0) {
						sb.append("&");
					} else {
						sb.append("?");
					}
					sb.append(pair.getName() + "=" + pair.getValue());
				}
				mUrl = sb.toString().trim();
			}
			request.setURI(new URI(mUrl));
			request.setHeaders(this.mHeaderList.toArray(new Header[this.mHeaderList.size()]));
			setDefaultParams();
			// 打印url和参数
			Log.d(TAG, "start connection url >>  " + mUrl.toString());
			for (BasicNameValuePair param : mRequestParams) {
				Log.d(TAG, param.getName() + " = " + param.getValue());
			}
			response = mClient.execute(request);
		} catch (SSLPeerUnverifiedException e) {
			e.printStackTrace();
		} catch (SSLException e) {
			e.printStackTrace();
		} catch (Exception e) {
			e.printStackTrace();
			throw new CLConnectionException(this.mUrl, 404);
		}
		return response;
	}

	protected HttpClient createHttpClient(String url) throws KeyManagementException,
			NoSuchAlgorithmException {
		HttpClient client = null;
		if (url != null) {
			if (url.startsWith("https")) {
				SchemeRegistry schemeRegistry = new SchemeRegistry();
				CLSSLSocketFactory sslSocketFactory = new CLSSLSocketFactory("");
				sslSocketFactory
						.setHostnameVerifier(CLSSLSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER);
				schemeRegistry.register(new Scheme("https", sslSocketFactory, 443));
				schemeRegistry.register(new Scheme("http", sslSocketFactory, 80));
				HttpParams httpParams = new BasicHttpParams();
				SingleClientConnManager mgr = new SingleClientConnManager(httpParams,
						schemeRegistry);
				client = new DefaultHttpClient(mgr, httpParams);
			} else {
				client = new DefaultHttpClient();
			}
		}
		return client;
	}

	/**
	 * 请求头类型
	 * 
	 * @author Jianfeng.Lao
	 * 
	 */
	public static enum RequestBobyType {
		/**
		 * 传统的application/x-www-form-urlencoded表单
		 */
		FORM_URLENCODED,
		/**
		 * multipart/form-data表单 可以上传文件
		 */
		FORM_DATA,
		/**
		 * Content-Type=application/json
		 */
		APP_JSON,
	}

	/**
	 * 请求方式
	 * 
	 * @author Jianfeng.Lao
	 * 
	 */
	public static enum RequestMethod {
		GET, POST, PUT
	}

	public void shutdown() {
		if (mClient != null) {
			mClient.getConnectionManager().shutdown();
		}
		if (mHttpRequest != null) {
			mHttpRequest.abort();
		}
	}

	private static void writeData(long processed, long contentLength, Object flag, InputStream is,
			OutputStream out, CLHttpProgressImpl listener) throws IOException {
		byte[] tmp = new byte[4096];
		int bytesRead = 0;
		long process = processed;
		while ((bytesRead = is.read(tmp)) != -1) {
			out.write(tmp, 0, bytesRead);
			process += bytesRead;
			if (listener != null) {
				listener.onProcessChange(process, contentLength, flag);
			}
		}
	}

	private static void writeData(long processed, long contentLength, Object flag, InputStream is,
			RandomAccessFile out, CLHttpProgressImpl listener) throws IOException {
		byte[] tmp = new byte[4096];
		int bytesRead = 0;
		long process = processed;
		while ((bytesRead = is.read(tmp)) != -1) {
			out.write(tmp, 0, bytesRead);
			process += bytesRead;
			if (listener != null) {
				listener.onProcessChange(process, contentLength, flag);
			}
		}
	}

	public static String getContentByResponse(HttpResponse response) throws IllegalStateException,IOException{
		InputStream input = null;
		String result = null;
		ByteArrayOutputStream baos = null;
		try {
			input = response.getEntity().getContent();
			if (input != null) {
				baos = new ByteArrayOutputStream();
				writeData(0, 0, null, input, baos, null);
			}
			result = new String(baos.toByteArray());
		} catch (IllegalStateException e) {
			throw e;
		} catch (IOException e) {
			throw e;
		} finally {
			CLFileUtil.closeIOStream(baos);
		}
		return result;
	}
}
